<?php

namespace Aspic\Preset;

use Aspic\Security\Security as S;

Trait OptionTrait {

	protected $optionNameToPropertyNameFunc = null;

	protected $optionSetMethodNameFunc = null;

	protected $optionGetMethodNameFunc = null;

	protected $reflection;

	protected function __construct(array $options = array()) {
		$this->reflection = new \ReflectionClass($this);
		$this->setOptions($options);

		$this->setOptionSetMethodNameFunc(function($optName) {
			return '_setOption_' . $optName;
		});

		$this->setOptionGetMethodNameFunc(function($optName) {
			return '_getOption_' . $optName;
		});

		$this->setOptionNameToPropertyNameFunc(function($optName) {
			return '_' . $optName;
		});
	}

	/**
	 * A function that take an option name as argument and return the
	 * corresponding property name
	 * @param callable $func
	 */
	public function setOptionNameToPropertyNameFunc(callable $func) {
		$this->optionNameToPropertyNameFunc = $func;
	}

	public function getPropertyNameFromOptionName($optName) {
		$func = $this->optionNameToPropertyNameFunc;

		return $func($optName);
	}

	/**
	 * A function that take an option name as argument and return the
	 * corresponding setter method name (does not means that method isset)
	 * @param callable $func
	 * @return string
	 */
	public function setOptionSetMethodNameFunc(callable $func) {
		$this->optionSetMethodNameFunc = $func;
	}

	public function getOptionSetMethodNameFunc() {
		return $this->optionSetMethodNameFunc;
	}

	/**
	 * A function that take an option name as argument and return the
	 * corresponding getter method name (does not means that method isset)
	 * @param callable $func
	 * @return string
	 */
	public function setOptionGetMethodNameFunc(callable $func) {
		$this->optionGetMethodNameFunc = $func;
	}

	public function getOptionGetMethodNameFunc() {
		return $this->optionGetMethodNameFunc;
	}

	public function setOptions(array $options) {
		foreach ($options as $name => $value) {
			if (property_exists($this, '_' . $name)) {
				$this->opt($name, $value);
			}
			else {
				throw new Exception('Property "' . $name . '" does not exist');
			}
		}
	}

	/**
	 * Read or write an option (property of the class with a starting "_" like "_opt")
	 * Ex: $obj->opt("content", "abcd"); will change the _content option (property)
	 * 
	 * @param bool $setOnlyIfNotSet Set the value as default value (if another value was previously set no changes are made)
	 */
	public function opt($optName, $optValue = null, $setOnlyIfNotSet = false) {
		if ($this->reflection === NULL) {
			throw new Exception('Constructor of "' . get_class() . '" has not been called');
		}

		$propertyName = $this->getPropertyNameFromOptionName($optName);

		if ($optValue === null) { // Read
			if (property_exists($this, $propertyName)) {
				$methodName = $this->getOptionSetMethodNameFunc($optName);

				if ($this->reflection->hasMethod($methodName) AND ! $this->reflection->getMethod($methodName)->isPublic()) {
					return $this->{$methodName}();
				}
				else {
					return $this->{$propertyName};
				}
			}
			else {
				throw new Exception('Property "' . $optName . '" does not exists');
			}
		}
		else { // Write
			if (property_exists($this, $propertyName)) {
				$methodName = $this->getOptionGetMethodNameFunc($optName);

				if (!$setOnlyIfNotSet OR is_null($this->opt($optName))) {
					if ($this->reflection->hasMethod($methodName) AND ! $this->reflection->getMethod($methodName)->isPublic()) {
						$this->{$methodName}($optValue);
					}
					else {
						$this->{$propertyName} = $optValue;
					}
				}
			}
			else {
				throw new Exception('Property "' . $optName . '" does not exists');
			}
		}
	}

	public function __get($optionName) {
		return $this->opt($optionName);
	}

	public function __set($optionName, $value) {
		return $this->opt($optionName, $value);
	}

}
